/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Event Spy.
 *
 * The Initial Developer of the Original Code is
 * Google Inc.
 * Portions created by the Initial Developer are Copyright (C) 2005
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * George Bradt
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var EventSpyEvents = {
    // Localization note: we don't translate event categories, names, and
    // properties since the spec defines them in English.

    gEventTypes: {
        composition: [ "composition",
                       "compositionstart",
                       "compositionend" ],

        contextmenu: [ "contextmenu" ],

        drag:        [ "dragenter",
                       "dragover",
                       "dragexit",
                       "dragdrop",
                       "draggesture" ],

        focus:       [ "focus",
                       "blur" ],

        form:        [ "submit",
                       "reset",
                       "change",
                       "select",
                       "input" ],

        key:         [ "keydown",
                       "keyup",
                       "keypress" ],

        load:        [ "load",
                       "beforeunload",
                       "unload",
                       "abort",
                       "error" ],

        mouse:       [ "mousedown",
                       "mouseup",
                       "click",
                       "dblclick",
                       "mouseover",
                       "mouseout" ],

        mousemotion: [ "mousemove" ],

        paint:       [ "paint",
                       "resize",
                       "scroll" ],

        scroll:      [ "overflow",
                       "underflow",
                       "overflowchanged" ],

        text:        [ "text" ],

        ui:          [ "DOMActivate",
                       "DOMFocusIn",
                       "DOMFocusOut" ],

        xul:         [ "popupshowing",
                       "popupshown",
                       "popuphiding",
                       "popuphidden",
                       "close",
                       "command",
                       "broadcast",
                       "commandupdate" ]
    },

    gTargetNode: null,

    handleEvent: function(event) {
        var shortDesc = event.type;

        switch(event.eventPhase) {

            case Event.CAPTURING_PHASE:
                shortDesc += "[CAPTURE]";
                break;

            case Event.AT_TARGET:
                shortDesc += "[TARGET]";
                break;

            case Event.BUBBLING_PHASE:
                shortDesc += "[BUBBLE]";
                break;
        }

        // Broken by DOMi 2.0.10.
        // DOMi Bug 643924 - JavaScript Object viewer shouldn't indefinitely cache object keys.
        // This bugfix, not mentioned in 2.0.10 release notes,
        // sees DOMi now using custom tree view instead of content tree view.
        // Therefore, Event Spy 0.4 will manage its own Event Log tree, instead of using DOMi scripts.
        // Removing those scripts will reduce the chances of further such problems.
        
        //viewer.addTreeItem(viewer.mTreeKids, shortDesc, event, event);
        
        var eventLogTreeKids = document.getElementById("spy-eventLogTreeKids");
        this.eventLogTreeJsm.addTreeItem(eventLogTreeKids, shortDesc, event, event, self);
    },

    handleBubbleEvent: function(event) {
        EventSpyEvents.handleEvent(event);
    },

    handleCaptureEvent: function(event) {
        EventSpyEvents.handleEvent(event);
    },

	// Called when the Event Log tree is clicked. Expand a closed tree item when its twisty is clicked.
    
    onClickEventLogTree: function (aEvent, aTree) {
        if ( aEvent.detail == 1 ) {
          // Get the row, col and child element at the point of mouse click.
          var tbo = aTree.treeBoxObject;
          var row = { }, col = { }, child = { };
          tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, child);
          
          if ( child.value == "twisty" && aTree.view.isContainerOpen(row.value) ) {
              this.eventLogTreeJsm.openTreeItem(aTree.view.getItemAtIndex(row.value), self);    
          }
        }
    },
    
    eventsOnLoad: function() {
		Components.utils.import("resource://eventspy/eventLogTree.jsm", this);
        var bundle = document.getElementById("spy-eventsBundle");
        var treeChildren = document.getElementById("spy-eventsTreeChildren");
        var item;
        var events;
        var gridRowCount = 2;
        var gridColumns = document.getElementById("spy-eventColumns");
        var gridColumn;

        for ( let category in this.gEventTypes ) {
            // Each grid column will contain 3 checkboxes.
            if ( ++gridRowCount == 3 ) {
                gridColumn = document.createElement("column");
                gridColumns.appendChild(gridColumn);
                gridRowCount = 0;
            }

            // Create a checkbox for each event category.
            item = document.createElement("checkbox");
            item.setAttribute("id", category + "Events");
            item.setAttribute("label", bundle.getString(category));
            gridColumn.appendChild(item);

            events = this.gEventTypes[category];

            // Create an event tree entry for each event in the category.
            for ( let j = 0; j < events.length; ++j ) {
                item = document.createElement("treeitem");
                item.setAttribute("label", events[j]);
                treeChildren.appendChild(item);
            }
        }

        // Target node is passed into the dialog.
        if ( window.arguments && window.arguments.length ) {
            if ( window.arguments[0] instanceof EventTarget ) {
                this.gTargetNode = window.arguments[0];
            }
        }

        // Set object/id description.
        var desc = bundle.getString("targetDescription") + " " + this.gTargetNode;
        var nodeId = "";

        if ( this.gTargetNode && this.gTargetNode.hasAttributes() ) {
            if ( this.gTargetNode.hasAttribute("id") ) {
                nodeId = this.gTargetNode.getAttribute("id");
                desc += " [id=" + nodeId + "]";
            }
            else {
                desc += " [" + bundle.getString("noId") + "]";
            }
        }

        document.getElementById("spy-nodeDescription").value = desc;
    },

    removeEventListeners: function() {
        let events;

        for ( let category in this.gEventTypes ) {
            events = this.gEventTypes[category];

            for ( let i = 0; i < events.length; ++i ) {
                this.gTargetNode.removeEventListener(events[i], EventSpyEvents.handleCaptureEvent, true);
                this.gTargetNode.removeEventListener(events[i], EventSpyEvents.handleBubbleEvent, false);
            }
        }
    },

    eventsOnClose: function() {
        if ( this.gTargetNode ) {
            EventSpyEvents.removeEventListeners();
        }
    },

    updateEventFilter: function(tree) {
        // Update our filter based on the currently-selected items in the list.
        if ( !this.gTargetNode ) {
            return;
        }

        EventSpyEvents.removeEventListeners();

        var view = tree.view;
        var rowCount = view.rowCount;
        var selection = view.selection;
        var column;

        // FF 1.0 wants column to be an id, 1.1 wants a column object,
        // which we can get from the tree element.
        if ( "columns" in tree ) {
            column = tree.columns.getColumnAt(0);
        }
        else {
            column = "spy-eventName";
        }

        var eventname;

        for ( let i = 0; i < rowCount; ++i ) {
            if ( selection.isSelected(i) ) {
                eventname = view.getCellText(i, column);
                this.gTargetNode.addEventListener(eventname, EventSpyEvents.handleCaptureEvent, true);
                this.gTargetNode.addEventListener(eventname, EventSpyEvents.handleBubbleEvent, false);
            }
        }
    },

    onGridCommand: function(event) {
        var checkbox = event.target;
        var id = checkbox.getAttribute("id");
        var category = id.substr(0, id.length - 6);  // Remove "Events"
        var events = this.gEventTypes[category];

        if ( !events ) {
            return;
        }

        // Since the events are grouped by type in the tree, we can compute the
        // range of items to select by locating this category in the list.

        var startRowCount = 0;
        var cat;
        //dump("category = " + category + "\n");

        for ( cat in this.gEventTypes ) {
            if ( cat == category ) {
              break;
            }

            //dump("add " + this.gEventTypes[cat].length + " rows for " + cat + "\n");
            startRowCount += this.gEventTypes[cat].length;
        }

        var tree = document.getElementById("spy-eventsTree");
        var selection = tree.view.selection;
        var rangeEnd = startRowCount + this.gEventTypes[cat].length - 1;

        if ( checkbox.checked ) {
            // We're selecting this category
            selection.rangedSelect(startRowCount, rangeEnd, true);
        }
        else {
            // We're clearing this category
            selection.clearRange(startRowCount, rangeEnd);
            EventSpyEvents.updateEventFilter(tree); // no select event is generated
        }
    },

    clearEventLog: function() {
        var treechildren = document.getElementById("spy-eventLogTreeKids");
        var treebox = document.getElementById("spy-eventLogTree").treeBoxObject;
        treebox.beginUpdateBatch();
        var child;

        while ( (child = treechildren.firstChild) ) {
            treechildren.removeChild(child);
        }

        treebox.endUpdateBatch();
    },

    // Based on code from Console2 extension (Philip Chee).
    toggleOnTop: function() {
        var bundle = document.getElementById("spy-eventsBundle");
        var xulWin = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                getInterface(Components.interfaces.nsIWebNavigation).
                QueryInterface(Components.interfaces.nsIDocShellTreeItem).
                treeOwner.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                getInterface(Components.interfaces.nsIXULWindow);

        xulWin.zLevel = ( xulWin.zLevel > xulWin.normalZ ) ?
                xulWin.normalZ : xulWin.raisedZ;

        document.getElementById("spy-pin").label = ( xulWin.zLevel > xulWin.normalZ ) ?
                bundle.getString("unpin") : bundle.getString("pin");
    },

    // Debug messages to console.

    debug: function(aMsg) {
        setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);
    }
};
